package com.christianposta.learnddd.domain.model.handling;

import java.util.Date;

import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

import com.christianposta.learnddd.domain.model.cargo.Cargo;
import com.christianposta.learnddd.domain.model.location.Location;
import com.christianposta.learnddd.domain.model.voyage.Voyage;
import com.christianposta.learnddd.domain.shared.DomainEvent;

public final class HandlingEvent implements DomainEvent<HandlingEvent>{

	private HandlingType type;
	private Voyage voyage;
	private Cargo cargo;
	private Location location;
	private Date completionTime;
	private Date registrationTime;

	/**
	 * Create a HandlingEvent for a HandlingType that allows voyages.
	 * 
	 * @param cargo
	 * @param completionTime
	 * @param registrationTime
	 * @param type
	 * @param location
	 * @param voyage
	 */
	public HandlingEvent(final Cargo cargo, 
						final Date completionTime,
						final Date registrationTime,
						final HandlingType type,
						final Location location,
						final Voyage voyage){
		
		Validate.notNull(cargo, "Cargo is required");
		Validate.notNull(completionTime, "Completion time is required");
		Validate.notNull(registrationTime, "Registration time is required");
		Validate.notNull(type, "A HandlingType is required for a HandlingEvent");
		Validate.notNull(location, "Location for this event is required");
		Validate.notNull(voyage, "Voyage cannot be null");
		
		if(type.prohibitsVoyage()){
			throw new IllegalArgumentException("Cannot create a HandlingEvent for this type [" + 
					type + "] because it cannot have a Voyage.");
		}
		
		this.cargo = cargo;
		this.completionTime = completionTime;
		this.registrationTime = registrationTime;
		this.type = type;
		this.location = location;
		this.voyage = voyage;
	}
	
	/**
	 * Create a HandlingEvent that cannot have a voyage associated with it
	 * @param cargo
	 * @param completionTime
	 * @param registrationTime
	 * @param type
	 * @param location
	 */
	public HandlingEvent(final Cargo cargo, 
						final Date completionTime,
						final Date registrationTime, 
						final HandlingType type,
						final Location location) {

		Validate.notNull(cargo, "Cargo is required");
		Validate.notNull(completionTime, "Completion time is required");
		Validate.notNull(registrationTime, "Registration time is required");
		Validate.notNull(type, "A HandlingType is required for a HandlingEvent");
		Validate.notNull(location, "Location for this event is required");

		if (type.requiresVoyage()) {
			throw new IllegalArgumentException(
					"Cannot create a HandlingEvent for this type [" + type
							+ "] because it must have a Voyage.");
		}

		this.cargo = cargo;
		this.completionTime = completionTime;
		this.registrationTime = registrationTime;
		this.type = type;
		this.location = location;
		this.voyage = null;
	}
	
	public HandlingType type(){
		return this.type;
	}
	
	public Cargo cargo(){
		return this.cargo;
	}
	
	public Voyage voyage(){
		return this.voyage;
	}
	
	public Location location(){
		return this.location;
	}
	
	public Date completionTime(){
		return new Date(this.completionTime.getTime());
	}
	
	public Date registrationTime(){
		return new Date(this.registrationTime.getTime());
	}
	
	
	public boolean sameEventAs(HandlingEvent other) {
		return other != null && new EqualsBuilder().append(this.type, other.type)
				.append(this.completionTime, other.completionTime)
				.append(this.registrationTime, other.registrationTime)
				.append(this.location, other.location)
				.append(this.cargo, other.cargo)
				.append(this.voyage, other.voyage)
				.isEquals();
	}
	
	@Override
	public boolean equals(Object obj) {
		if(obj == this) return true;
		if(obj == null && getClass() != obj.getClass()) return false;
		
		final HandlingEvent other = (HandlingEvent)obj;
		return sameEventAs(other);
		
	}
	
	@Override
	public int hashCode() {
		return new HashCodeBuilder().append(this.type)
				.append(this.completionTime)
				.append(this.registrationTime)
				.append(this.location)
				.append(this.cargo)
				.append(this.voyage)
				.toHashCode();
	}

}
